<?php

error_reporting(E_ALL);
set_time_limit(0); // 设置超时时间为无限,防止超时
date_default_timezone_set('Asia/shanghai');

class MyServer {

    protected $master; //当前链接socket协议
    protected $sockets = []; //所有socket链接

    const LISTEN_SOCKET_NUM = 9; //最大连接数

    public function __construct($host = '127.0.0.1', $port = 8080) {
        try {
            $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
            // 设置IP和端口重用,在重启服务器后能重新使用此端口;
            socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1);
            // 将IP和端口绑定在服务器socket上;
            socket_bind($this->master, $host, $port);
            // listen函数使用主动连接套接口变为被连接套接口，使得一个进程可以接受其它进程的请求，从而成为一个服务器进程。
            // 在TCP服务器编程中listen函数把进程变为一个服务器，并指定相应的套接字变为被动连接,其中的能存储的请求不明的socket数目。
            socket_listen($this->master, self::LISTEN_SOCKET_NUM);
        } catch (Exception $e) {
            $err_code = socket_last_error();
            $err_msg = socket_strerror($err_code);
            echo $err_code . '=================>' . $err_msg;
        };

        $this->sockets[0] = ['socket' => $this->master];

        print_r($this->master);

        while (1) {
            try {
                $this->DoServer();
            } catch (Exception $e) {
                
            }
        }
    }

    public function DoServer() {
        $write = $except = NULL;
        $sockets = array_column($this->sockets, "socket");
        $result = socket_select($sockets, $write, $except, NULL);

        if ($result === false) {
            echo "socket监听好函数出错";
            return;
        }

        foreach ($sockets as $socket) {
            if ($socket == $this->master) {
                $client = socket_accept($socket);
                if ($client === false) {
                    echo "socket_accept error";
                    continue;
                } else {
                    $this->Connect($client);
                    continue;
                }
            } else {
                $bytes = socket_recv($socket, $buffer, 204800, 0);
                if($bytes<9){//关闭链接
                    
                }else{
                    //判断是否通过握手
                    if(!$this->sockets[(int)$socket]['handshake']){                    
                        //进入握手方法
                        $this->HandShake($socket,$buffer);
                        continue;
                    }else{
                        //解密接收到的数据
                        $recv_msg = $this->Parse($buffer);
                    }
                    //print_r($recv_msg);
                    $msg = $this->DealMsg($recv_msg);
                    socket_write($socket, $msg,  strlen($msg));
                    /*array_unshift($recv_msg, "receive_msg");
                    $recv_msg = $this->dealmsg($socket,$recv_msg);*/
                }
            }
        }
    }

    public function Connect($socket) {
        socket_getpeername($socket, $ip, $port);
        $socket_info = array(
            'socket' => $socket,
            'uname' => '',
            'handshake' => false,
            'ip' => $ip,
            'port' => $port
        );
        $this->sockets[(int) $socket] = $socket_info;
    }
    
    /**
     * 握手方法
     * @param type $socket
     * @param type $buffer
     */
    public function HandShake($socket,$buffer){
        // 获取到客户端的升级密匙
        $line_with_key = substr($buffer, strpos($buffer, 'Sec-WebSocket-Key:') + 18);
        $key = trim(substr($line_with_key, 0, strpos($line_with_key, "\r\n")));
        // 生成升级密匙,并拼接websocket升级头
        $upgrade_key = base64_encode(sha1($key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); // 升级key的算法
        $upgrade_message = "HTTP/1.1 101 Switching Protocols\r\n";
        $upgrade_message .= "Upgrade: websocket\r\n";
        $upgrade_message .= "Sec-WebSocket-Version: 13\r\n";
        $upgrade_message .= "Connection: Upgrade\r\n";
        $upgrade_message .= "Sec-WebSocket-Accept:" . $upgrade_key . "\r\n\r\n";
        socket_write($socket, $upgrade_message, strlen($upgrade_message)); // 向socket里写入升级信息
        $this->sockets[(int) $socket]['handshake'] = true;
        // 向客户端发送握手成功消息,以触发客户端发送用户名动作;
        $msg = array(
            'type' => 'handshake',
            'content' => 'done',
        );
        $msg = $this->build(json_encode($msg));
        socket_write($socket, $msg, strlen($msg));
        return true;
    }
    
    /**
     * 解析数据
     * @param $buffer
     * @return bool|string
     */
    private function parse($buffer) {
        $decoded = '';
        $len = ord($buffer[1]) & 127;
        if ($len === 126) {
            $masks = substr($buffer, 4, 4);
            $data = substr($buffer, 8);
        } else if ($len === 127) {
            $masks = substr($buffer, 10, 4);
            $data = substr($buffer, 14);
        } else {
            $masks = substr($buffer, 2, 4);
            $data = substr($buffer, 6);
        }
        for ($index = 0; $index < strlen($data); $index++) {
            $decoded .= $data[$index] ^ $masks[$index % 4];
        }
        return json_decode($decoded, true);
    }

    /**
     * 将普通信息组装成websocket数据帧
     * @param $msg
     * @return string
     */
    private function build($msg) {
        $frame = array();
        $frame[0] = '81';
        $len = strlen($msg);
        if ($len < 126) {
            $frame[1] = $len < 16 ? '0' . dechex($len) : dechex($len);
        } else if ($len < 65025) {
            $s = dechex($len);
            $frame[1] = '7e' . str_repeat('0', 4 - strlen($s)) . $s;
        } else {
            $s = dechex($len);
            $frame[1] = '7f' . str_repeat('0', 16 - strlen($s)) . $s;
        }
        $data = '';
        $l = strlen($msg);
        for ($i = 0; $i < $l; $i++) {
            $data .= dechex(ord($msg{$i}));
        }
        $frame[2] = $data;
        $data = implode('', $frame);
        return pack("H*", $data);
    }
    
    public function DealMsg($msg){
        print_r($msg);
        $response = [];
        $type = $msg['type'];
        $content = $msg['content'];
        switch ($type){
            case "login":
                $response['type'] = 'login';
                $response['content'] = $content;
                //$response['user_list'] = $user_list;
            break;
            case "message":
                $response['type'] = 'message';
                $response['content'] = $content;
            break;
        }
        return $this->build(json_encode($response));
    }

}

$server = new MyServer('192.168.10.134');

